package top.laoshuzi.cloudplatform.camera;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.NonNull;
import android.util.DisplayMetrics;
import android.util.Size;

import com.gm.tool.utils.FileUtil;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import top.laoshuzi.cloudplatform.App;
import top.laoshuzi.cloudplatform.tools.L;

/**
 * Created by mouse on 2017/12/19.
 */

public class CameraHandler {


    private static final int MAX_PREVIEW_WIDTH = 640;
    private static final int MAX_PREVIEW_HEIGHT = 480;

    private static final Size ASPECT_RATIO = new Size(4, 3);

    private Context context;
    private CameraManager mCameraManager;
    private CameraDevice mCameraDevice;
    private CameraCaptureSession mCaptureSession;
    private CaptureRequest.Builder mCaptureBuilder;

    private HandlerThread mCameraThread;
    private Handler mCameraHandler;

    private String mCameraId;
    private boolean mFlashSupported;

    private int screenWidth;
    private int screenHeight;

    private Size mPictureSize;
    private File mPictureFile;
    private ImageReader mImageReader;

    private Size mVideoSize;
    private File mVideoFile;
    private volatile boolean mIsRecordingVideo;
    private MediaRecorder mMediaRecorder;

    private CameraHandler() {
        context = App.getContext();

        mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);

        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        screenWidth = dm.widthPixels;
        screenHeight = dm.heightPixels;

        new File(CameraConfig.PIC_PATH_TEMP).mkdirs();
        new File(CameraConfig.REC_PATH_TEMP).mkdirs();
    }

    private static class InstanceHolder {
        private static CameraHandler mCamera = new CameraHandler();
    }

    public static CameraHandler getInstance() {
        return InstanceHolder.mCamera;
    }

    //打开相机
    @SuppressLint("MissingPermission")
    public void openCamera() {

        startCameraThread();

        try {
            setUpCameraOutputs(screenWidth, screenHeight);
            //配置拍照
            setUpImageReader(mPictureSize);

            mCameraManager.openCamera(mCameraId,
                    new CameraDevice.StateCallback() {
                        @Override
                        public void onOpened(@NonNull CameraDevice cameraDevice) {
                            mCameraDevice = cameraDevice;
                            L.d("相机打开");
                        }

                        @Override
                        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
                            closeCaptureSession();
                            cameraDevice.close();
                        }

                        @Override
                        public void onError(@NonNull CameraDevice cameraDevice, int i) {
                            L.e("相机打开错误 code:" + i);
                            closeCaptureSession();
                            cameraDevice.close();
                        }

                        @Override
                        public void onClosed(@NonNull CameraDevice camera) {
                            mCameraDevice = null;
                        }
                    },
                    mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void setUpCameraOutputs(int width, int height) throws CameraAccessException {
        L.w("setUpCameraOutputs W:" + width + "   H:" + height);

        //选择相机
        String[] cameraIdList = mCameraManager.getCameraIdList();
        if (cameraIdList.length < 1) {
            L.d("No cameras found");
            return;
        }
        String cameraId = cameraIdList[0];

        //判断流配图
        CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
        StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        if (map == null) {
            L.d("No stream configuration map");
            return;
        }

        //检查闪光灯是否支持。
        Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
        mFlashSupported = available == null ? false : available;
        L.d("The camera " + (mFlashSupported ? "not is" : "is") + " supported Flash led");

        mCameraId = cameraId;
        L.d("Using camera id " + mCameraId);

        //最佳比例
        mPictureSize = chooseOptimalSize(
                map.getOutputSizes(ImageFormat.JPEG),
                width,
                height,
                screenWidth,
                screenHeight,
                ASPECT_RATIO);
        mVideoSize = chooseOptimalSize(
                map.getOutputSizes(MediaRecorder.class),
                width,
                height,
                screenWidth,
                screenHeight,
                ASPECT_RATIO);
        for (Size size : map.getOutputSizes(ImageFormat.JPEG))
            L.w("W:" + size.getWidth() + "   H:" + size.getHeight());
        L.w("PictureSize W:" + mPictureSize.getWidth() + "   H:" + mPictureSize.getHeight());
        for (Size size : map.getOutputSizes(MediaRecorder.class))
            L.w("W:" + size.getWidth() + "   H:" + size.getHeight());
        L.w("VideoSize W:" + mVideoSize.getWidth() + "   H:" + mVideoSize.getHeight());

        L.d(" 相机可用 ");
    }

    private void setUpImageReader(Size pictureSize) {
        mImageReader = ImageReader.newInstance(
                pictureSize.getWidth(),
                pictureSize.getHeight(),
//                ImageFormat.YUV_420_888,
                ImageFormat.JPEG,
                        /*maxImages*/2
        );
        mImageReader.setOnImageAvailableListener(
                new ImageReader.OnImageAvailableListener() {
                    @Override
                    public void onImageAvailable(ImageReader reader) {
                        mPictureFile = new File(
                                CameraConfig.PIC_PATH_TEMP,
                                new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date())
                        );
                        mCameraHandler.post(new ImageSaver(reader.acquireNextImage(), mPictureFile));
                    }
                },
                mCameraHandler
        );
    }

    private void setUpMediaRecorder(Size videoSize) {

        if (mMediaRecorder == null) {
            mMediaRecorder = new MediaRecorder();
        } else {
            mMediaRecorder.reset();
        }

        mVideoFile = new File(
                CameraConfig.REC_PATH +
                        File.separator +
                        String.valueOf(new Date().getTime()) +
                        ".mp4.cache"
        );

        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        mMediaRecorder.setOutputFile(mVideoFile.getAbsolutePath());
        mMediaRecorder.setAudioSamplingRate(44100);
        mMediaRecorder.setVideoEncodingBitRate(768000);
        mMediaRecorder.setVideoFrameRate(30);
        mMediaRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight());
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
//        mMediaRecorder.setOrientationHint(0);
        try {
            mMediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //关闭相机
    public void closeCamera() {
        closeCaptureSession();
        if (mCameraDevice != null) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        if (mImageReader != null) {
            mImageReader.close();
            mImageReader = null;
        }
        if (mMediaRecorder != null) {
            if (mIsRecordingVideo)
                stopRecordingVideo();
            mMediaRecorder.release();
            mMediaRecorder = null;
        }
        stopCameraThread();
        L.d("相机关闭");
    }

    //创建开始相机后台线程
    private void startCameraThread() {
        //线程初始化
        mCameraThread = new HandlerThread("Camera2");
        mCameraThread.start();
        mCameraHandler = new Handler(mCameraThread.getLooper());
    }

    //销毁停止相机后台线程
    private void stopCameraThread() {
        //线程回收
        mCameraThread.quitSafely();
        try {
            mCameraThread.join();
            mCameraThread = null;
            mCameraHandler = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //拍照
    public void takePicture() {
        if (mCameraDevice == null) {
            L.d("相机已关闭");
            return;
        }
        L.d("拍照");

        try {
            mCameraDevice.createCaptureSession(
                    Collections.singletonList(mImageReader.getSurface()),
                    new CameraCaptureSession.StateCallback() {
                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                            if (mCameraDevice == null) {
                                return;
                            }
                            mCaptureSession = cameraCaptureSession;
                            //捕获
                            captureStillPicture();
                        }

                        @Override
                        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                            L.w("相机配置失败");
                        }
                    },
                    null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    //捕获
    private void captureStillPicture() {
        if (mCaptureSession == null) {
            return;
        }
        L.d("捕获");

        try {
            CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            builder.addTarget(mImageReader.getSurface());
            // 自动对焦
            builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
            // 自动曝光
//            setAutoFlash(builder);

            mCaptureSession.capture(
                    builder.build(),
                    new CameraCaptureSession.CaptureCallback() {
                        @Override
                        public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                            L.d("拍照完成");

                            session.close();
                            mCaptureSession = null;
                        }
                    },
                    null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    //开始录制
    public void startRecordingVideo() {
        if (mCameraDevice == null) {
            L.d("相机已关闭");
            return;
        }

        closeCaptureSession();

        //配置录像
        setUpMediaRecorder(mVideoSize);

        try {
            mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
            mCaptureBuilder.addTarget(mMediaRecorder.getSurface());

            mCameraDevice.createCaptureSession(
                    Arrays.asList(mMediaRecorder.getSurface()),
                    new CameraCaptureSession.StateCallback() {
                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                            if (mCameraDevice == null)
                                return;

                            mCaptureSession = cameraCaptureSession;

                            //录制
                            mIsRecordingVideo = true;
                            try {
                                mCaptureSession.setRepeatingRequest(
                                        mCaptureBuilder.build(),
                                        null,
                                        mCameraHandler);
                            } catch (CameraAccessException e) {
                                e.printStackTrace();
                            }
                            mMediaRecorder.start();

                            L.d("开始录制");
                        }

                        @Override
                        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                            L.w("相机配置失败");
                        }
                    },
                    mCameraHandler
            );

        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    //停止录制
    public void stopRecordingVideo() {
        L.d("停止录制");
        try {
            mCaptureSession.stopRepeating();
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        closeCaptureSession();
        mMediaRecorder.stop();
        mIsRecordingVideo = false;

        String name = mVideoFile.getName().substring(0, mVideoFile.getName().lastIndexOf("."));
        FileUtil.renameFile(CameraConfig.REC_PATH, mVideoFile.getName(), name);
    }

    //关闭当前会话
    private void closeCaptureSession() {
        if (mCaptureSession != null) {
            try {
                L.d("关闭会话");
                mCaptureSession.close();
            } catch (Exception ex) {
                L.e("Could not close capture session");
            }
            mCaptureSession = null;
        }
    }

    //自动曝光
    private void setAutoFlash(CaptureRequest.Builder requestBuilder) {
        if (mFlashSupported) {
            requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                    CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
        }
    }

    //最佳比例
    private static Size chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {

        List<Size> bigEnough = new ArrayList<>();
        List<Size> notBigEnough = new ArrayList<>();

        int w = aspectRatio.getWidth();
        int h = aspectRatio.getHeight();
        for (Size option : choices) {
            if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
                    option.getHeight() == option.getWidth() * h / w) {
                if (option.getWidth() >= textureViewWidth &&
                        option.getHeight() >= textureViewHeight) {
                    bigEnough.add(option);
                } else {
                    notBigEnough.add(option);
                }
            }
        }

        if (bigEnough.size() > 0) {
            return Collections.min(bigEnough, new CompareSizesByArea());
        } else if (notBigEnough.size() > 0) {
            return Collections.max(notBigEnough, new CompareSizesByArea());
        } else {
            L.e("找不到最佳比例");
            return choices[0];
        }
    }

    private static class ImageSaver implements Runnable {

        /**
         * The JPEG image
         */
        private final Image mImage;
        /**
         * The mPictureFile we save the image into.
         */
        private final File mFile;

        public ImageSaver(Image image, File file) {
            mImage = image;
            mFile = file;
        }

        @Override
        public void run() {
            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            FileOutputStream output = null;
            try {
                output = new FileOutputStream(mFile);
                output.write(bytes);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mImage.close();
                if (null != output) {
                    try {
                        output.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

    private static class CompareSizesByArea implements Comparator<Size> {
        @Override
        public int compare(Size lhs, Size rhs) {
            // We cast here to ensure the multiplications won't overflow
            return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
                    (long) rhs.getWidth() * rhs.getHeight());
        }

    }
}
